// Logic Classes.
// ==============

// Collision Detector.
class pp_collisionDetector extends Thread {
  ArrayList<pp_gameObject> objectArray = new ArrayList<pp_gameObject>();
  
  pp_collisionDetector() {}  
  void start() {super.start();}
  
  void run() {
    // Go through the list and detect collisions.
    if (objectArray.size() != 0) {
      for (int i = 0; i < objectArray.size(); i++) {
        
        // Select object 1.
        pp_gameObject obj1 = objectArray.get(i);
        
        // Test if object 1 is flagged as destroyed. Remove it from list.
        if (obj1.destroyed) {
          objectArray.remove(i);
        
        // Else, proceed with collision testing.
        } else {      
          for (int k = i+1; k < objectArray.size(); k++) {
          
            // Select object 2.
            pp_gameObject obj2 = objectArray.get(k);              
          
            // See if they collide.
            if (pp_objCollision(obj1, obj2)) {
            
              // Run the collision event in each object in event of collision.
              obj1.other = obj2;
              obj1.collide();
              
              obj2.other = obj1;
              obj2.collide();
            }
            
            // If there's a velocity vector, see if it will collide with anything next frame.
            if ((obj1.xSpeed != 0) || (obj1.ySpeed != 0) || (obj2.xSpeed != 0) || (obj2.ySpeed != 0)) {
              if (pp_objColPredictive(obj1, obj2)) {
            
                // Run the collision event in each object in event of collision.
                obj1.otherPredict = obj2;
                obj1.colPredict();
              
                obj2.otherPredict = obj1;
                obj2.colPredict();
              }
            }
          }
        }
      }
    }
    
    // Sync with current frame.
    pp_waitThread = false;
  }
}

// Game Object Abstract Class.
class pp_gameObject {
  int x, y, xOffset, yOffset, origin, depth, objWidth, objHeight, alpha; 
  int bBoxWidth, bBoxHeight, bBoxXOffset, bBoxYOffset;
  int objFrames, waitFrames, currentFrame, currentWait;  // Animation variables.
  float xSpeed, ySpeed;
  boolean destroyed, visible, lockBBox;
  String sprite, type;
  pp_gameObject other, otherPredict;
  
  // Constructor.
  pp_gameObject() {
    visible = true;
    lockBBox = false;
    type = "Object";
    sprite = null;
    alpha = 255;
    
    // Add object to collide detect array.
    pp_collider.objectArray.add(this);
  }
  
  void setX(int PosX) {x = PosX;}
  void setY(int PosY) {y = PosY;}
  void setAlpha(int Alpha) {alpha = Alpha;}
  void setDepth(int RenderDepth) {depth = RenderDepth;}  
  void setWidth(int Width) {objWidth = Width; pp_refreshBBox(this);}
  void setHeight(int Height) {objHeight = Height; pp_refreshBBox(this);}
  void setType(String TypeString) {type = TypeString;}
  
  void setOrigin(int Ori) {
    origin = Ori;
    xOffset = pp_getXOffset(origin, objWidth);
    yOffset = pp_getYOffset(origin, objHeight);
    pp_refreshBBox(this);
  }
  
  void setSprite(String SpriteFile) {
    if (spr.get(SpriteFile) != null) {
      sprite = SpriteFile;
      pp_sprite pix = spr.get(sprite);
      objWidth = pix.pixWidth;
      objHeight = pix.sprite.height;
      objFrames = pix.pixFrames;
      waitFrames = pix.waitFrames;
    } else {
      sprite = null;
      objWidth = 0;
      objHeight = 0;
      objFrames = 0;
      waitFrames = 0;
    }
    currentFrame = 0;
    xOffset = pp_getXOffset(origin, objWidth);
    yOffset = pp_getYOffset(origin, objHeight);
    pp_refreshBBox(this);
  }
  
  void animate() {
    // Update frames according to FPS.
    if (currentWait < waitFrames) {
      currentWait++;
    } else {
      currentWait = 0;
      if (currentFrame < objFrames) {
        currentFrame++;
      } else {
        currentFrame = 0;
      }
    }
  }
  
  void draw() {pp_drawSprite(x - xOffset, y - yOffset, depth, currentFrame, alpha, sprite);}
  
  void move() {
    x = int(x + xSpeed);
    y = int(y + ySpeed);
  }
  
  void init() {}           // Initializing method.
  void update() {}         // Drawing method.
  void collide() {}        // Run when game object collides with another game object.
  void colPredict() {}     // Run when game object will collide into something next frame.
  void keyPressed() {}     // Run when a key is pressed.
  void keyReleased() {}    // Run when a key is released.
  void mousePressed() {}   // Run when a mouse button is pressed.
  void mouseReleased() {}  // Run when a mouse button is released.
  void destroy() {destroyed = true;}
}

// Logic Functions.
// ================

// Boolean-Int converter.
int pp_toInt (boolean b) { return b ? 1 : 0; }
boolean pp_toBoolean (int i) { return (i > 0) ? true : false; }

// Pos/Neg to 1/0 converter.
int pp_signToInt (int i) {return (i > 0) ? 1 : 0;}
int pp_intToSign (int i) {return (i > 0) ? 1 : -1;}

// Mouse X-position in Room.
int pp_mouseX() {return int((mouseX/pp_pixelSize) + pp_displayX);}

// Mouse y-position in Room.
int pp_mouseY() {return int((mouseY/pp_pixelSize) + pp_displayY);}

// Calculate distance between nearest bounding box faces of objects.
int pp_vertDist (pp_gameObject obj1, pp_gameObject obj2) {
  int result1 = (obj2.y - obj2.bBoxYOffset) - (obj1.y - obj1.bBoxYOffset + obj1.bBoxHeight);
  int result2 = (obj2.y - obj2.bBoxYOffset + obj2.bBoxHeight) - (obj1.y - obj1.bBoxYOffset);
  return (min(abs(result1), abs(result2)) == abs(result1)) ? result1 : result2;
}
int pp_horzDist (pp_gameObject obj1, pp_gameObject obj2) {
  int result1 = (obj2.x - obj2.bBoxXOffset) - (obj1.x - obj1.bBoxXOffset + obj1.bBoxWidth);
  int result2 = (obj2.x - obj2.bBoxXOffset + obj2.bBoxWidth) - (obj1.x - obj1.bBoxXOffset);
  return (min(abs(result1), abs(result2)) == abs(result1)) ? result1 : result2;
}
float pp_dist (pp_gameObject obj1, pp_gameObject obj2) {return sqrt(sq(pp_vertDist(obj1,obj2)) + sq(pp_horzDist(obj1,obj2)));}

// Collision Detection Functions
// =============================

// Collision Single Point. (return object collided with)
pp_gameObject pp_ptCollision (int x, int y) {return pp_ptCollision (x, y, "");}
pp_gameObject pp_ptCollision (int x, int y, String ignore) {
  pp_gameObject result = null;
  for (pp_gameObject obj : pp_objects) {
    if (!obj.type.equals(ignore)){
      if (
      ((obj.x - obj.bBoxXOffset) < x) &&
      ((obj.x + obj.bBoxWidth - obj.bBoxXOffset) > x) &&
      ((obj.y - obj.bBoxYOffset) < y) &&
      ((obj.y + obj.bBoxHeight - obj.bBoxYOffset) > y)
      ) {
        result = obj;
        break;
      }
    }
  }
  return result;
}

// Horizontal Collision (by Points)
boolean pp_horzCollision(int x1, int x2, int x3, int x4) {
  if (((x1 < x3) && (x2 < x3)) || ((x4 < x1) && (x4 < x2))) {
    return false;
  } else { return true; }
}

// Horizontal Collision (by Object)
boolean pp_objHorzCollision (pp_gameObject obj1, pp_gameObject obj2) {
  return pp_horzCollision (
    obj1.x - obj1.bBoxXOffset,
    obj1.x + obj1.bBoxWidth - obj1.bBoxXOffset,
    obj2.x - obj2.bBoxXOffset,
    obj2.x + obj2.bBoxWidth - obj2.bBoxXOffset
  );
}

// Vertical Collision (by Points)
boolean pp_vertCollision(int y1, int y2, int y3, int y4) {
  if (((y1 < y3) && (y2 < y3)) || ((y4 < y1) && (y4 < y2))) {
    return false;
  } else { return true; }
}

// Vertical Collision (by Object)
boolean pp_objVertCollision (pp_gameObject obj1, pp_gameObject obj2) {
  return pp_vertCollision (
    obj1.y - obj1.bBoxYOffset,
    obj1.y + obj1.bBoxHeight - obj1.bBoxYOffset,
    obj2.y - obj2.bBoxYOffset,
    obj2.y + obj2.bBoxHeight - obj2.bBoxYOffset
  );
}

// Current-frame object collision.
boolean pp_objCollision(pp_gameObject obj1, pp_gameObject obj2) {
  if (pp_toInt(pp_objHorzCollision(obj1, obj2)) + pp_toInt(pp_objVertCollision(obj1, obj2)) == 2){return true;}
  else { return false; }
}

// Predictive object collision.
boolean pp_objColPredictive(pp_gameObject obj1, pp_gameObject obj2) {
  if (
    pp_toInt(pp_horzCollision (
      obj1.x - obj1.bBoxXOffset + int(obj1.xSpeed),
      obj1.x + obj1.bBoxWidth - obj1.bBoxXOffset + int(obj1.xSpeed),
      obj2.x - obj2.bBoxXOffset + int(obj2.xSpeed),
      obj2.x + obj2.bBoxWidth - obj2.bBoxXOffset + int(obj2.xSpeed))) + 
    pp_toInt(pp_vertCollision (
      obj1.y - obj1.bBoxYOffset + int(obj1.ySpeed),
      obj1.y + obj1.bBoxHeight - obj1.bBoxYOffset + int(obj1.ySpeed),
      obj2.y - obj2.bBoxYOffset + int(obj2.ySpeed),
      obj2.y + obj2.bBoxHeight - obj2.bBoxYOffset + int(obj2.ySpeed))) == 2
  ) {
    return true;
  } else { return false; }
}

// Update all objects.
void pp_updateObjects() {
  for (int i = 0; i < pp_objects.size(); i++) { 
    
    // Get object.
    pp_gameObject obj = pp_objects.get(i);
    
    // If destroyed, remove from list.
    if (obj.destroyed){
      pp_objects.remove(i);
      i--;
      
    // Else, update and render.
    } else {
      obj.update();
      
      // If there's a velocity, update object position.
      if ((obj.ySpeed != 0) || (obj.xSpeed != 0)){
        obj.move();
      }
      
      // Test if object has a sprite.
      if (obj.sprite != null){
      
        // If more than 1 frame, animate.
        if (obj.objFrames > 0) {obj.animate();}
      
        // Render sprite if not invisible and on-screen.
        if (obj.visible){
          obj.draw();
        }
      }
    }
  }
}

// Clear all objects.
void pp_clearAllObjects() {
  pp_objects.clear();
  pp_collider.objectArray.clear();
  pp_effects.clear();
  pp_emitters.clear();
  pp_decals.clear();
  pp_graphics.clear();
}

// Refresh bounding box.
void pp_refreshBBox(pp_gameObject obj) {
  // Bounding box.
  // If bounding box is not locked, change the bounding box dimensions.
  if (!obj.lockBBox) {
    obj.bBoxWidth = obj.objWidth;
    obj.bBoxHeight = obj.objHeight;
    obj.bBoxXOffset = obj.xOffset;
    obj.bBoxYOffset = obj.yOffset;
  } else {
        
    // If no current sprite, change bounding box anyway.
    if (obj.sprite == null) {
      obj.bBoxWidth = obj.objWidth;
      obj.bBoxHeight = obj.objHeight;
      obj.bBoxXOffset = obj.xOffset;
      obj.bBoxYOffset = obj.yOffset;
    }
  }
}

// Get object reference.
pp_gameObject pp_getObject(String name) {
  pp_gameObject obj = null;
  for (pp_gameObject object : pp_objects) {
    if (object.type.equals(name)){
      obj = object;
      break;
    }
  }  
  return obj;
}

// Get X Offset.
int pp_getXOffset (int origin, int objWidth) {
  int xOffset;
  
  switch (origin) {
    
    // Top-left.
    case 1:
      xOffset = 0;
      break;
        
    // Top-center.
    case 2:
      xOffset = objWidth/2;
      break;
        
    // Top-right.
    case 3:
      xOffset = objWidth;
      break;
        
    // Center-left.
    case 4:
      xOffset = 0;
      break;
      
    // Center.
    case 5:
      xOffset = objWidth/2;
      break;
      
    // Center-right.
    case 6:
      xOffset = objWidth;
      break;
      
    // Bottom-left.
    case 7:
      xOffset = 0;
      break;
      
    // Bottom-center.
    case 8:
      xOffset = objWidth/2;
      break;
      
    // Bottom-right.
    case 9:
      xOffset = objWidth;
      break;
      
    default:
      xOffset = 0;
      break;
  }
  return xOffset;
}

// Get Y Offset.
int pp_getYOffset (int origin, int objHeight) {
  int yOffset;
  
  switch (origin) {
    
    // Top-left.
    case 1:
      yOffset = 0;
      break;
        
    // Top-center.
    case 2:
      yOffset = 0;
      break;
        
    // Top-right.
    case 3:
      yOffset = 0;
      break;
        
    // Center-left.
    case 4:
      yOffset = objHeight/2;
      break;
      
    // Center.
    case 5:
      yOffset = objHeight/2;
      break;
      
    // Center-right.
    case 6:
      yOffset = objHeight/2;
      break;
      
    // Bottom-left.
    case 7:
      yOffset = objHeight;
      break;
      
    // Bottom-center.
    case 8:
      yOffset = objHeight;
      break;
      
    // Bottom-right.
    case 9:
      yOffset = objHeight;
      break;
      
    default:
      yOffset = 0;
      break;
  }
  return yOffset;
}
